﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using msilgc.common;
using msilgc.common.Attributes;
using msilgc.driver.cuda;
using msilgc.runtime.Platforms;

namespace msilgc.runtime {
    
    public class Builder {
        public IMethodHandle CompileStaticMethod(MethodInfo method, PlatformType platform, bool debug)
        {
            if (!method.IsStatic)
            {
                throw new NotSupportedException("only supports static methods at this time");
            }
            if(PlatformType.CUDA == platform)
            {
                string className;
                string code = new MarshallingCodeGenerator().WriteCode(method, out className, debug);
                FileInfo dotNetDllFile = CSharpCompilerHelperFactory.Create().CreateDotNetDll(code);
                Assembly proxyAssembly = Assembly.LoadFile(dotNetDllFile.FullName);
                Type proxyType = proxyAssembly.GetType(className);
                ConstructorInfo ctor = proxyType.GetConstructor(new[] { typeof(MethodInfo) });
                IMethodHandle methodHandle = (IMethodHandle)ctor.Invoke(new[] { method });
                methodHandle.GeneratedKernelCode += code;
                return methodHandle;
            }
            else if(PlatformType.NATIVE == platform)
            {
                return new DefaultMethodHandle(method);
            }
            else
            {
                throw new NotSupportedException("Platform " + platform + " is not yet supported");
            }
        }

        public void CompileAssemblyAot(FileInfo assemblyFile, DirectoryInfo outputFolder) {
            Logger.WriteLine("Reading assembly...");
            var assembly = Assembly.LoadFrom(assemblyFile.FullName);
            var methods = FindMethodsToCompile(assembly);
            if (!methods.Any()) {
                throw new Exception("No methods found to compile");
            }
            Logger.WriteLine("Generating .NET wrapper DLL...");
            var marshallingCode = new AotMarshallingCodeGenerator().WriteCode(methods);
            CSharpCompilerHelperFactory.Create().CreateDotNetDll(marshallingCode, outputFolder);

            Logger.WriteLine("Generating CUDA native DLL...");

            string compilerMsg, kernelCode;
            new CudaCompilerHelper().CreateCudaImplementation(outputFolder, methods, out compilerMsg, out kernelCode, false);

            Logger.WriteLine(compilerMsg);
        }

        private static IEnumerable<MethodInfo> FindMethodsToCompile(Assembly assembly) {
            var methods =
                from type in assembly.GetTypes()
                from method in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
                where method.IsStatic
                let matchingAttributes =
                    from attribute in method.GetCustomAttributes(typeof (CompileWithMsilgcAttribute), true)
                    select attribute
                where matchingAttributes.Any()
                select method;
            return methods.ToArray();
        }
    }
}
